
/**
 * @file:       trigger_scheduler.hpp
 * @author:     WubinWang
 * @contact:    wubinstu@163.com
 * @date:       2025-01-01
 * @license:    MIT License
 *
 * Copyright (c) 2025 WubinWang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * DISCLAIMER:
 * 1. User acknowledges all application risks and consequences arising from usage
 *    of this software are exclusively borne by the user.
 * 2. The original author shall not be held liable for any direct, indirect,
 *    incidental, special, exemplary or consequential damages.
 * 3. No warranty of fitness for particular purpose or non-infringement is provided. */


#ifndef __TRIGGER_SCHEDULER_HPP_
#define __TRIGGER_SCHEDULER_HPP_

#include <algorithm>
#include <atomic>
#include <cstdint>
#include <functional>
#include <limits>
#include <mutex>
#include <vector>

/// @brief Trigger_Scheduler_ct: 一个轻量的触发器调度器
/// 使用说明:
/// 1.在主循环中定期调用 Trigger_Scheduler_ct::process (current_time_ms);
/// 2.调用 Trigger_Scheduler_ct::registerTask (...) 注册任务
/// 3.(一切就绪, 触发器将自动调度执行对应的任务)
/// 4.如果有需要, 调用 Trigger_Scheduler_ct::unregisterTask (task_id) 卸载任务
class Trigger_Scheduler_ct {

  public:
    using Time_ANY_t           = std::uint64_t;
    using Task_ID_t            = std::uint32_t;
    using Task_Repeat_t        = std::uint32_t;
    using Task_Main_ft         = std::function<void ()>;
    using Task_Condition_ft    = std::function<bool ()>;

    using Task_Zeroth_Cycle_et = enum class Task_Zeroth_Cycle_et : std::uint8_t {
        ZEROTH_CYCLE_ON,
        ZEROTH_CYCLE_OFF,
    };

    using Task_Condition_Type_et = enum class Task_Condition_Type_et : std::uint8_t {
        CONDITION_ET,
        CONDITION_LT,
    };

    using Task_Expire_Action_et = enum class Task_Expire_Action_et : std::uint8_t {
        EXPIRE_AUTO_CLEAN,
        EXPIRE_JUST_KEEP,
    };

    using Task_Item_st = struct Task_Item_st {

        explicit Task_Item_st () noexcept                               = default;
        Task_Item_st (const Task_Item_st & _other) noexcept             = default;
        Task_Item_st (Task_Item_st && _other) noexcept                  = default;
        Task_Item_st & operator= (const Task_Item_st & _other) noexcept = default;
        Task_Item_st & operator= (Task_Item_st && _other) noexcept      = default;

        inline auto operator== (const Task_Item_st & _other) const noexcept -> bool { return static_cast<bool> (this->_m_tid == _other._m_tid); }

        auto clean () noexcept -> void;

        bool                   _m_enabled        = false;               // 任务使能标志
        bool                   _m_running        = false;               // 任务是否正在运行
        Task_ID_t              _m_tid            = K_INVALID_TASK;      // 任务 ID
        Time_ANY_t             _m_interval       = K_NO_INTERVAL;       // 任务周期
        Time_ANY_t             _m_deadline       = 0;                   // 下一次的触发时间
        Task_Repeat_t          _m_remaining      = K_REPEAT_INFINITE;   // 剩余触发次数 (默认无限触发)
        Task_Main_ft           _m_action         = nullptr;             // 触发时执行的入口
        Task_Zeroth_Cycle_et   _m_zeroth_cycle   = K_ZEROTH_CYCLE_OFF;  // 任务是否"一上来"就立刻执行一次, (固定周期有效)
        Task_Condition_ft      _m_condition      = nullptr;             // 条件检查
        Task_Condition_Type_et _m_condition_type = K_CONDITION_ET;      // 条件检查: 是边沿触发而不是水平触发
        Task_Expire_Action_et  _m_expire_action  = K_EXPIRE_JUST_KEEP;  // 任务完成时是否自动清理
        bool                   _m_condition_last = false;               // 条件检查: 用于上升沿 (false -> true)
    };

    using Task_Builder_st = struct Task_Builder_st {
        Task_Item_st task {};

        Task_Builder_st & enabled (bool value) noexcept;
        Task_Builder_st & running (bool value) noexcept;
        Task_Builder_st & tid (Task_ID_t value) noexcept;
        Task_Builder_st & interval (Time_ANY_t value) noexcept;
        Task_Builder_st & deadline (Time_ANY_t value) noexcept;
        Task_Builder_st & repeat (Task_Repeat_t value) noexcept;
        Task_Builder_st & action (Task_Main_ft fn) noexcept;
        Task_Builder_st & zeroth_cycle (Task_Zeroth_Cycle_et value) noexcept;
        Task_Builder_st & condition (Task_Condition_ft fn) noexcept;
        Task_Builder_st & condition_type (Task_Condition_Type_et value) noexcept;
        Task_Builder_st & expire_action (Task_Expire_Action_et value) noexcept;
        Task_Builder_st & condition_last (bool value) noexcept;

        [[nodiscard]] Task_Item_st build () const noexcept;
    };

    constexpr const static inline Task_ID_t  K_INVALID_TASK                 = 0;  // 0 表示无效的任务 ID
    constexpr const static inline Time_ANY_t K_NO_INTERVAL                  = 0;  // 0 表示无固定周期

    constexpr const static inline Task_Repeat_t K_REPEAT_INFINITE           = std::numeric_limits<Task_Repeat_t>::max ();  // 最大表示无限重复
    constexpr const static inline Task_Repeat_t K_REPEAT_ONESHOT            = 1;                                           // 1 表示 OneShot

    constexpr const static inline Task_Zeroth_Cycle_et K_ZEROTH_CYCLE_ON    = Task_Zeroth_Cycle_et::ZEROTH_CYCLE_ON;   // 表示开启第零个周期
    constexpr const static inline Task_Zeroth_Cycle_et K_ZEROTH_CYCLE_OFF   = Task_Zeroth_Cycle_et::ZEROTH_CYCLE_OFF;  // 表示关闭第零个周期

    constexpr const static inline Task_Condition_Type_et K_CONDITION_ET     = Task_Condition_Type_et::CONDITION_ET;  // ET 表示边沿触发
    constexpr const static inline Task_Condition_Type_et K_CONDITION_LT     = Task_Condition_Type_et::CONDITION_LT;  // LT 表示水平触发

    constexpr const static inline Task_Expire_Action_et K_EXPIRE_AUTO_CLEAN = Task_Expire_Action_et::EXPIRE_AUTO_CLEAN;  // 自动清理失效的任务
    constexpr const static inline Task_Expire_Action_et K_EXPIRE_JUST_KEEP  = Task_Expire_Action_et::EXPIRE_JUST_KEEP;   // 保留失效的任务

    constexpr const static inline auto SET_INTERVAL (const Time_ANY_t & _time) noexcept { return _time; }

    constexpr const static inline auto SET_REPEAT (const Task_Repeat_t & _repeat) noexcept { return _repeat; }

    const static inline auto SET_ACTION (const Task_Main_ft & _action) noexcept { return _action; }

    const static inline auto SET_CONDITION (const Task_Condition_ft & _condition) noexcept { return _condition; }

    /// @brief 返回任务的数量
    inline auto taskCounts () const noexcept { return this->_m_tasks.size (); }

    /// @brief 获取当前时间的引用
    inline auto currentTime () noexcept -> Time_ANY_t & { return this->_m_current_time; }

    /// @brief 检查任务是否合理, 判断标准见 Trigger_Scheduler_ct::registerTask () 的@warning函数说明
    static auto taskValid (const Task_Item_st & _task) noexcept -> bool;

    /// @brief 根据任务 ID 查找和返回任务
    auto getTask (Task_ID_t _tid, bool _blocking = false) noexcept -> const Task_Item_st *;

    /// @brief 使能调度器 (默认即使能)
    auto enable () noexcept -> void;

    /// @brief 失能调度器
    auto disable () noexcept -> void;


    /// @brief 注册一个任务
    /// @param _action: 任务的触发动作
    /// @param _interval: 任务的触发周期, 默认为 0 表示"无固定周期", 只进行条件触发
    /// @param _repeats: 任务执触发次数, 默认为 MAX 表示无限次执行, 为 1 时表示 OneShot 延时 _interval 后执行一次
    /// @param _zeroth_cycle: 第零个周期, 对于周期触发的任务, 是否需要"一上来"就触发一次, 默认为 false 表示等到下一个周期
    /// @param _condition: 任务触发的前置条件, 默认不设置表示无条件触发, 如果设置了任务必须等待条件满足和周期满足才会执行
    /// @param _condition_type: (_condition 为非 nullptr 时有效) 指定任务条件触发的类型"是边沿触发还是水平触发", 边沿触发指的是条件从 false->true 的上升沿触发一次, 水平触发指的是"只要条件一直是 true, 就一直触发"
    /// @param _expire_action: (任务触发次数有限时有效) 当有限次数的任务已经完成时, 指定是否需要自动卸载任务, 默认不自动卸载
    /// @param _blocking: 当任务表被加锁保护时是否阻塞等待, 如果不等待且任务表被保护时则直接返回 0, 默认阻塞等待
    /// @return 任务ID, 最小值为 1, 如果传入参数不合理或其他原因则返回 0 表示"未创建", 比如你试图创建一个既不是周期控制又没有条件控制的任务, 或者任务的 _action 为 nullptr
    /// @attention 你可以设置如下的任务类型:
    /// 01. 固定周期无条件触发无限次
    /// 02. 固定周期无条件触发有限次 (比如一次, 即 OneShot, 这时候的固定周期可以视为"延时")
    /// 03. 固定周期且上升沿时(ET)触发无限次
    /// 04. 固定周期且上升沿时(ET)触发有限次 (比如一次, 即 OneShot, 这时候的固定周期可以视为"距离执行的最小时间 (因为还会判断条件)")
    /// 05. 固定周期且条件真时(LT)触发无限次 (如果条件一直为真, 则等效于第一种情况)
    /// 06. 固定周期且条件真时(LT)触发有限次 (如果条件一直为真, 则等效于第二种情况)
    /// 07. 无固定周期, 只判断条件, 条件出现上升沿时(ET)触发无限次
    /// 08. 无固定周期, 只判断条件, 条件出现上升沿时(ET)触发有限次
    /// 09. 无固定周期, 只判断条件, 条件真时(LT)触发无限次 (如果条件一直为真, 则等效于第一种情况)
    /// 10. 无固定周期, 只判断条件, 条件真时(LT)触发有限次 (如果条件一直为真, 则等效于第二种情况)
    /// @warning 你不应该试图创建这样的任务, 否则本函数返回 0 表示忽略这种不合理的请求 (由 Trigger_Scheduler_ct::taskValid () 判断):
    /// 01. 任务没有 _action (为 nullptr)
    /// 02. 任务的 _repeats 为 0, 即一次也不会执行
    /// 03. 任务的 _interval 为 0 (无固定周期) 并且 任务没有 _condition (为 nullptr)
    /// 04. 任务的 _repeat 为 K_REPEAT_INFINITE (无限次数触发) 并且指定了 _auto_clean 自动清理
    /// 05. 任务的 _interval 为 0 (无固定周期) 并且设置了 _zeroth_cycle
    auto registerTask (Task_Main_ft           _action,
                       Time_ANY_t             _interval       = K_NO_INTERVAL,
                       Task_Repeat_t          _repeats        = K_REPEAT_INFINITE,
                       Task_Zeroth_Cycle_et   _zeroth_cycle   = K_ZEROTH_CYCLE_OFF,
                       Task_Condition_ft      _condition      = nullptr,
                       Task_Condition_Type_et _condition_type = K_CONDITION_ET,
                       Task_Expire_Action_et  _expire_action  = K_EXPIRE_JUST_KEEP,
                       bool                   _blocking       = true) noexcept -> Task_ID_t;
    auto registerTask (Task_Item_st _task, bool _blocking = true) noexcept -> Task_ID_t;


    /// @brief 更新已存在的任务
    /// @param _tid 任务 ID
    /// @param 其他参数规则和 Trigger_Scheduler_ct::registerTask () 一致
    /// @return 更新成功返回 true, 更新失败 (如任务不存在, 或者传入的参数不合理等其他原因) 返回 false
    auto updateTask (Task_ID_t              _tid,
                     Task_Main_ft           _action,
                     Time_ANY_t             _interval       = K_NO_INTERVAL,
                     Task_Repeat_t          _repeats        = K_REPEAT_INFINITE,
                     Task_Zeroth_Cycle_et   _zeroth_cycle   = K_ZEROTH_CYCLE_OFF,
                     Task_Condition_ft      _condition      = nullptr,
                     Task_Condition_Type_et _condition_type = K_CONDITION_ET,
                     Task_Expire_Action_et  _expire_action  = K_EXPIRE_JUST_KEEP,
                     bool                   _blocking       = true) noexcept -> bool;
    auto updateTask (const Task_Item_st & _task, bool _blocking = true) noexcept -> bool;


    /// @brief 根据 tid 卸载一个任务
    /// @param _tid: 任务ID
    /// @param _blocking: 如果任务表被保护, 是否需要阻塞等待, 默认阻塞
    /// @return 是否卸载成功 (如果任务不存在或者以非阻塞尝试卸载正在执行的任务则返回失败)
    auto unregisterTask (Task_ID_t _tid, bool _blocking = true) noexcept -> bool;


    /// @brief 强制清除所有任务
    auto forceClear (bool _blocking = true) noexcept -> bool;


    /// @brief 在主循环中调用该函数并实时传入系统时间, 该函数执行过程中会对任务表加锁保护
    /// @param _current_time 系统时间 (任意的单位, 和任务周期的单位统一即可), 如果为默认的 0 表示不使用该参数, 那么时间可以从 Trigger_Scheduler_ct::currentTime () 传入
    auto process (Time_ANY_t _current_time = 0) noexcept -> void;


  private:

    /// @brief 带有时间溢出的安全检查函数, 检查 _deadline 是否已经抵达
    static inline bool timePassedOrEqual (Time_ANY_t _deadline, Time_ANY_t _current) { return static_cast<std::int64_t> (_current - _deadline) >= 0; }

    std::atomic<bool> _m_enabled              = true;  // 调度器状态, 默认使能, 失能时 process 不会生效且无法添加新任务
    Task_ID_t         _m_max_tid              = 1;     // 最大的任务 ID, 从 1 自增
                                                       // 如果中间有任务失效并卸载了, 其占用的 ID 不会重新利用
                                                       // 因此该变量指示了 "累计" 创建的任务数量, 类似于进程 PID
                                                       // 如果要获取任务数量, 应该使用 Trigger_Scheduler_ct::taskCounts () 函数
    Time_ANY_t                _m_current_time = 0;     // 当前系统时间
    std::vector<Task_Item_st> _m_tasks {};             // 任务表
    std::mutex                _m_lock {};              // 保护任务表, 为多线程提供兼容支持
};

#endif
